package org.jboss.resteasy.core.registry;

import org.jboss.resteasy.util.PathHelper;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public abstract class Expression implements Comparable<Expression>
{
   // [^?] is in expression to ignore non-capturing group
   public static final Pattern GROUP = Pattern.compile("[^\\\\]\\([^?]");
   protected String pathExpression;
   protected String regex;
   protected Pattern pattern;
   protected List<Group> groups = new ArrayList<Group>();
   protected int literalCharacters;
   protected int numCapturingGroups;
   protected int numNonDefaultGroups;

   public Expression(String segment, String additionalRegex)
   {
      this.pathExpression = segment;
      String replacedCurlySegment = PathHelper.replaceEnclosedCurlyBraces(segment);
      literalCharacters = PathHelper.URI_PARAM_PATTERN.matcher(replacedCurlySegment).replaceAll("").length();

      String[] split = PathHelper.URI_PARAM_PATTERN.split(replacedCurlySegment);
      Matcher withPathParam = PathHelper.URI_PARAM_PATTERN.matcher(replacedCurlySegment);
      int i = 0;
      StringBuffer buffer = new StringBuffer();
      if (i < split.length) buffer.append(Pattern.quote(split[i++]));
      int groupNumber = 1;

      while (withPathParam.find())
      {
         String name = withPathParam.group(1);
         buffer.append("(");
         if (withPathParam.group(3) == null)
         {
            buffer.append("[^/]+");
            groups.add(new Group(groupNumber++, name, true));
         }
         else
         {
            String expr = withPathParam.group(3);
            expr = PathHelper.recoverEnclosedCurlyBraces(expr);
            buffer.append(expr);
            numNonDefaultGroups++;
            groups.add(new Group(groupNumber++, name));
            groupNumber += groupCount(expr);
         }
         buffer.append(")");
         if (i < split.length) buffer.append(Pattern.quote(split[i++]));
      }
      if (additionalRegex != null) buffer.append(additionalRegex);
      regex = buffer.toString();
      pattern = Pattern.compile(getRegex());
   }

   public int compareTo(Expression expression)
   {
      // as per spec sort first by literal characters, then numCapturing groups, then num non-default groups

      if (literalCharacters > expression.literalCharacters) return -1;
      if (literalCharacters < expression.literalCharacters) return 1;

      if (numCapturingGroups > expression.numCapturingGroups) return -1;
      if (numCapturingGroups < expression.numCapturingGroups) return 1;

      if (numNonDefaultGroups > expression.numNonDefaultGroups) return -1;
      if (numNonDefaultGroups < expression.numNonDefaultGroups) return 1;
      return 0;
   }

   /**
    * Find the number of groups in the regular expression
    * don't count escaped '('
    *
    * @param regex
    * @return
    */
   protected static int groupCount(String regex)
   {
      regex = " " + regex; // add a space because GROUP regex trans to match a non-preceding slash.
      // if the grouping characters in the range block ignore them.
      int idxOpen = regex.indexOf('[');
      if (idxOpen != -1)
      {
         int idxClose = regex.indexOf(']', idxOpen);
         if (idxClose != -1)
         {
            regex = regex.substring(0, idxOpen) + regex.substring(idxClose + 1);
         }
      }
      Matcher matcher = GROUP.matcher(regex);
      int groupCount = 0;
      while (matcher.find()) groupCount++;
      return groupCount;
   }

   public int getNumGroups()
   {
      return groups.size();
   }

   public String getRegex()
   {
      return regex;
   }

   public String getPathExpression()
   {
      return pathExpression;
   }

   public Pattern getPattern()
   {
      return pattern;
   }

   protected static class Group
   {
      int group;
      String name;
      boolean storePathSegment;

      protected Group(int group, String name)
      {
         this.group = group;
         this.name = name;
      }

      protected Group(int group, String name, boolean storePathSegment)
      {
         this.group = group;
         this.name = name;
         this.storePathSegment = storePathSegment;
      }
   }
}
